package boardgamer.yavalath.common;

import boardgamer.common.GameStateChange;
import boardgamer.common.net.PlayerId;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * @author garysoed
 */
public abstract class YavalathGameStateChange implements GameStateChange {

  private static final long serialVersionUID = -8672571185730415939L;

  public enum Event {
    JOIN_GAME,
    START_GAME,
    ADD_TOKEN,
    PLAYER_WINS,
    PLAYER_LOSES,
    DRAW
  }

  private final Event event;
  @Nullable
  protected final PlayerId player;

  protected YavalathGameStateChange(Event event, PlayerId player) {
    this.event = checkNotNull(event, "event");
    this.player = player;
  }

  public Event getEvent() {
    return event;
  }

  public PlayerId getPlayer() {
    return player;
  }

  public static class PlayerJoinGame extends YavalathGameStateChange {

    private static final long serialVersionUID = 4525071927637926481L;

    private final YavalathPlayerState playerState;

    public PlayerJoinGame(PlayerId player, YavalathPlayerState playerState) {
      super(Event.JOIN_GAME, player);
      this.playerState = checkNotNull(playerState, "playerState");
    }

    public YavalathPlayerState getPlayerState() {
      return playerState;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof PlayerJoinGame)) {
        return false;
      }

      PlayerJoinGame that = (PlayerJoinGame) obj;
      return Objects.equal(this.playerState, that.playerState);
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(playerState);
    }

    @Override
    public String toString() {
      return Objects.toStringHelper(this)
          .add("playerState", playerState)
          .toString();
    }
  }

  public static class StartGame extends YavalathGameStateChange {

    private static final long serialVersionUID = -4841650834462140364L;

    public StartGame() {
      super(Event.START_GAME, (PlayerId) null);
    }

    @Override
    public boolean equals(Object obj) {
      return obj instanceof StartGame;
    }

    @Override
    public int hashCode() {
      return 0;
    }

    @Override
    public String toString() {
      return Objects.toStringHelper(this)
          .toString();
    }
  }

  public static class PlayerAddToken extends YavalathGameStateChange {

    private static final long serialVersionUID = 6968840425894556178L;

    private final int x;
    private final int y;

    public PlayerAddToken(PlayerId player, int x, int y) {
      super(Event.ADD_TOKEN, player);
      this.x = x;
      this.y = y;
    }

    protected PlayerAddToken(Event event, PlayerId player, int x, int y) {
      super(event, player);
      this.x = x;
      this.y = y;
    }

    public int getX() {
      return x;
    }

    public int getY() {
      return y;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof PlayerAddToken)) {
        return false;
      }
      return deepEqual((PlayerAddToken) this);
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(x, y);
    }

    @Override
    public String toString() {
      return Objects.toStringHelper(this)
          .add("x", x)
          .add("y", y)
          .toString();
    }

    protected boolean deepEqual(PlayerAddToken that) {
      return Objects.equal(this.x, that.x)
          && Objects.equal(this.y, that.y);
    }
  }

  public static class PlayerWins extends YavalathGameStateChange {

    private static final long serialVersionUID = -5050245074955746663L;

    // Null if the player wins because everyone else loses
    @Nullable
    private final Set<YavalathTile> winningTiles;

    public PlayerWins(PlayerId player, @Nullable Collection<YavalathTile> winningTiles) {
      super(Event.PLAYER_WINS, player);
      this.winningTiles = winningTiles == null ? null : ImmutableSet.copyOf(winningTiles);
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof PlayerWins)) {
        return false;
      }
      PlayerWins that = (PlayerWins) obj;
      return Objects.equal(this.player, that.player)
          && Objects.equal(this.winningTiles, that.winningTiles);
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(player, winningTiles);
    }

    @Override
    public String toString() {
      return Objects.toStringHelper(this)
          .add("player", player)
          .add("winningTiles", winningTiles)
          .toString();
    }
  }

  public static class PlayerLoses extends YavalathGameStateChange {

    private static final long serialVersionUID = -7324756273207814828L;

    private final Set<YavalathTile> losingTiles;

    public PlayerLoses(PlayerId player, Collection<YavalathTile> losingTiles) {
      super(Event.PLAYER_LOSES, player);
      this.losingTiles = ImmutableSet.copyOf(checkNotNull(losingTiles, "losingTiles"));
    }

    public Set<YavalathTile> getLosingTiles() {
      return losingTiles;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof PlayerLoses)) {
        return false;
      }
      PlayerLoses that = (PlayerLoses) obj;
      return Objects.equal(this.player, that.player)
          && Objects.equal(this.losingTiles, that.losingTiles);
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(player, losingTiles);
    }

    @Override
    public String toString() {
      return Objects.toStringHelper(this)
          .add("player", player)
          .add("losingTiles", losingTiles)
          .toString();
    }
  }

  public static class Draw extends YavalathGameStateChange {

    private static final long serialVersionUID = -4340881462768353758L;

    public Draw() {
      super(Event.DRAW, (PlayerId) null);
    }

    @Override
    public boolean equals(Object obj) {
      return obj instanceof Draw;
    }

    @Override
    public int hashCode() {
      return 0;
    }

    @Override
    public String toString() {
      return Objects.toStringHelper(this)
          .toString();
    }
  }
}
